<?php 

namespace pmvc\mvc\security;

use pmvc\util\StringUtil;

use pmvc\ioc\Ordered;
use pmvc\mvc\RequestWrapper;
use pmvc\mvc\Request;
use pmvc\mvc\Response;

class SecurityRequestWrapper
	implements RequestWrapper,
	Ordered {

	private $order						= Ordered::LOWEST_PRECEDENCE;

	private $authenticationProviders	= Array();
	private $authenticators				= Array();
	private $authenticationListeners	= Array();

	private $urlPatternRoleMap			= Array();
	private $anonymousRoles				= Array();

	private $nextUrlParamName			= "nextUrl";
	private $loginFailedParamName		= "loginFailed";
	
	private $loginFormUrl				= "/login";
	private $loginFailedUrl				= "/loginFailed";
	private $loginSuccessUrl			= "/loginSuccess";
	private $accessDeniedUrl			= "/accessDenied";
	private $logoutSuccessUrl			= "/logoutSuccess";

	private $loginActionUrlPattern		= "^/login.do";
	private $logoutActionUrlPattern		= "^/logout.do";
	

    public function beforeRequest(Request $request, Response $response) {

    	// get requested url
    	$url = $request->getRequestedUrl();

    	// perform login
    	if (StringUtil::matches($this->loginActionUrlPattern, $url, FALSE)) {
    		$this->performLogin($request, $response);
    		return FALSE;

    	// perform logout?
    	} else if (StringUtil::matches($this->logoutActionUrlPattern, $url, FALSE)) {
    		$this->performLogout($request, $response);
			return FALSE;
    	}

    	// is this a secure page?
    	return $this->checkAccess($request, $response);
    }

    private function checkAccess(Request $request, Response $response) {

    	// get requested url
    	$url 			= $request->getRequestedUrl();

    	// get user data
    	$isLoggedIn 	= SecurityUtil::isLoggedIn($request);
    	$user			= ($isLoggedIn) ? SecurityUtil::getCurrentUser($request) : NULL;
    	$userRoles		= ($isLoggedIn) ? $user->getRoles() : $this->anonymousRoles;

    	// check accesss
    	foreach ($this->urlPatternRoleMap as $pattern => $roles) {
    		if (StringUtil::matches($pattern, $url, FALSE)) {
    			foreach ($roles as $role) {
    				if (!in_array($role, $userRoles)) {
    					$redirectUrl = !SecurityUtil::isLoggedIn($request)
    						? $this->loginFormUrl : $this->accessDeniedUrl;
    					if ($this->nextUrlParamName!=NULL) {
	    					$redirectUrl .= (strpos($redirectUrl, "?")===FALSE)?"?":"&";
	    					$redirectUrl .= urlencode($this->nextUrlParamName)."=".urlencode($url);
    					}
    					$response->sendRedirect($redirectUrl);
    					return FALSE;
    				}
    			}
    		}
    	}

    	// good to go
    	return TRUE;
    }

    private function performLogin(Request $request, Response $response) {
 
    	// attempt to authenticate
    	$user = NULL;
    	foreach ($this->authenticationProviders as $authenticationProvider) {
    		$authentication = $authenticationProvider->getAuthentication($request, $response);
    		if ($authentication!=NULL) {
    			$user = $this->performAuthentication($authentication);
    			if ($user!=NULL) {
    				break;
    			}
    		}
    	}

    	// set user roles
    	if ($user!=NULL) {
    		SecurityUtil::setCurrentUser($request, $user);
    		foreach ($this->authenticationListeners as $listener) {
    			$listener->onLogin($request, $response, $user);
    		}
    		$response->sendRedirect($request->getParameter(
    			$this->nextUrlParamName, $this->loginSuccessUrl));

    	// clear the user
    	} else {
    		$redirectUrl = $this->loginFailedUrl
    			. ((strpos($this->loginFailedUrl, "?")===FALSE)?"?":"&")
    			. urldecode($this->loginFailedParamName)."=1";
    		foreach ($this->authenticationListeners as $listener) {
    			$listener->onLoginFailed($request, $response);
    		}
    		$response->sendRedirect($redirectUrl);
    	}
    }

    private function performLogout(Request $request, Response $response) {
    	if (SecurityUtil::isLoggedIn($request)) {
    		$user = SecurityUtil::getCurrentUser($request);
	    	foreach ($this->authenticationListeners as $listener) {
	    		$listener->onLogout($request, $response, $user);
	    	}
    	}
    	SecurityUtil::clear($request);
    	$response->sendRedirect($request->getParameter(
    		$this->nextUrlParamName, $this->logoutSuccessUrl));
    }
    
	private function performAuthentication(Authentication $authentication) {

    	foreach ($this->authenticators as $authenticator) {
    		if ($authenticator->supports($authentication)) {
    			$user = $authenticator->authenticate($authentication);
	    		if ($user!=NULL) {
	    			return $user;
	    		}
    		}
    	}

    	// wa-wa-wa :(
    	return NULL;
    }

    public function afterRequest(Request $request, Response $response) {
    	// no-op
    }

	public function setAuthenticationProviders($authenticationProviders) {
		$this->authenticationProviders = $authenticationProviders;
	}

	public function setAuthenticationProvider($authenticationProvider) {
		$this->setAuthenticationProviders(Array($authenticationProvider));
	}

	public function setAuthenticationListeners($authenticationListeners) {
		$this->authenticationListeners = $authenticationListeners;
	}

	public function setAuthenticationListener($authenticationListeners) {
		$this->setAuthenticationListeners(Array($authenticationListeners));
	}

	public function setUrlPatternRoleMap(Array $urlPatternRoleMap) {
		$this->urlPatternRoleMap = $urlPatternRoleMap;
	}

	public function setUrlPatternRoleMapNames(Array $urlPatternRoleMapNames) {
		$urlPatternRoleMap = Array();
		foreach ($urlPatternRoleMapNames as $key => $values) {
			$urlPatternRoleMap[$key] = explode(",", $values);
		}
		$this->setUrlPatternRoleMap($urlPatternRoleMap);
	}

	public function setAnonymousRoles(Array $anonymousRoles) {
		$this->anonymousRoles = $anonymousRoles;
	}

	public function setAnonymousRoleNames($anonymousRoleNames) {
		$this->setAnonymousRoles(explode(",", $anonymousRoleNames));
	}

	public function setNextUrlParamName($authenticationProviders) {
		$this->authenticationProviders = $authenticationProviders;
	}
	
	public function setLoginFormUrl($loginFormUrl) {
		$this->loginFormUrl = $loginFormUrl;
	}

	public function setLoginActionUrlPattern($loginActionUrlPattern) {
		$this->loginActionUrlPattern = $loginActionUrlPattern;
	}

	public function setLoginFailedUrl($loginFailedUrl) {
		$this->loginFailedUrl = $loginFailedUrl;
	}

	public function setLoginSuccessUrl($loginSuccessUrl) {
		$this->loginSuccessUrl = $loginSuccessUrl;
	}

	public function setAccessDeniedUrl($accessDeniedUrl) {
		$this->accessDeniedUrl = $accessDeniedUrl;
	}
	
	public function setLogoutActionUrlPattern($logoutActionUrlPattern) {
		$this->logoutActionUrlPattern = $logoutActionUrlPattern;
	}

	public function setLogoutSuccessUrl($logoutSuccessUrl) {
		$this->logoutSuccessUrl = $logoutSuccessUrl;
	}

    public function setAuthenticators(Array $authenticators) {
    	$this->authenticators = $authenticators;
    }

    public function setAuthenticator(Authenticator $authenticator) {
    	$this->setAuthenticators(Array($authenticator));
    }

    public function setOrder($order) {
    	$this->order = $order;
    }

    public function getOrder() {
    	return $this->order;
    }

}

?>